package edu.ncu.websocket;

import com.alibaba.druid.sql.visitor.functions.Concat;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import edu.ncu.entity.Contact;
import edu.ncu.entity.User;
import edu.ncu.service.UserService;
import edu.ncu.utils.ChatMessageUtils;
import edu.ncu.utils.MessageUtils;
import edu.ncu.utils.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.socket.*;

import java.io.IOException;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Socket处理器
 */
@Component
public class MyWebSocketHandler implements WebSocketHandler {
	//用于保存HttpSession与WebSocketSession的映射关系
	// 以userId为键，以websocketsession为值
	public static final Map<Integer, WebSocketSession> userSocketSessionMap;

	@Autowired
	UserService userService;
	@Autowired
	RedisUtil redisUtil ;
	@Autowired
	ChatMessageUtils chatMessage ;
	int friendId = 0;
	public void setFriendId(int friendId) {
		this.friendId = friendId ;
	}
	
	static {
		//juc包下ConcurrentHashMap map结构，支持高并发，效率高，而且线程安全
		userSocketSessionMap = new ConcurrentHashMap<Integer, WebSocketSession>();
	}

	/**
	 * 建立连接后,把登录用户的id写入WebSocketSession
	 * 并且检查是否有离线消息，如果有，那么将离线消息依次发送出去
	 */
	@Override
	public void afterConnectionEstablished(WebSocketSession session)
			throws Exception {
		User user = (User) session.getAttributes().get("user");
		int userId = user.getUserId() ;
		if (userSocketSessionMap.get(userId) == null) {
			System.out.println("登录用户的id写入WebSocketSession");
			userSocketSessionMap.put(userId, session);
			// 建立连接时。需要获取离线消息并依次发送
			// off_message:userId
			List<Contact> list = getOffMessage(userId);
			for (Contact contact : list) {
				// 依次将离线消息发送出去
				// TODO
				// 只需要发送senderId == friendId的消息
				if (friendId == contact.getSenderId()) {
					sendMessageToUser(contact) ;
				}
			}
			// 离线消息发送出去之后，清除一部分消息
			redisUtil.changeMessage(userId);
		}
	}

	public List<Contact> getOffMessage(int usedId) {
		return redisUtil.getOffMessageList(usedId) ;
	}


	/**
	 * 消息处理，在客户端通过Websocket API发送的消息会经过这里，然后进行相应的处理
	 */
	@Override
	public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
		System.out.println("消息处理");
		if(message.getPayloadLength()==0)
			return;

		Contact msg=new Gson().fromJson(message.getPayload().toString(),Contact.class);
		User receiver = userService.findByUserId(msg.getReceiverId()) ;
		User sender = userService.findByUserId(msg.getSenderId()) ;
		msg.setSender(sender);
		msg.setReceiver(receiver);
		msg.setSendTime(new Date());

		System.out.println("消息开始发送");
		sendMessageToUser(msg);
		System.out.println("消息发送成功");
	}

	/**
	 * 消息传输错误处理
	 */
	@Override
	public void handleTransportError(WebSocketSession session,
                                     Throwable exception) throws Exception {
		System.out.println("消息传输错误"+exception);
		if (session.isOpen()) {
			session.close();
		}
		Iterator<Entry<Integer, WebSocketSession>> it =
				userSocketSessionMap.entrySet().iterator();
		// 移除当前抛出异常用户的Socket会话
		while (it.hasNext()) {
			Entry<Integer, WebSocketSession> entry = it.next();
			if (entry.getValue().getId().equals(session.getId())) {
				userSocketSessionMap.remove(entry.getKey());
				break;
			}
		}
	}

	/**
	 * 关闭连接后
	 */
	@Override
	public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
		System.out.println("Websocket:" + session.getId() + "已经关闭");
		Iterator<Entry<Integer, WebSocketSession>> it
				= userSocketSessionMap.entrySet().iterator();
		// 移除当前用户的Socket会话
		while (it.hasNext()) {
			Entry<Integer, WebSocketSession> entry = it.next();
			if (entry.getValue().getId().equals(session.getId())) {
				userSocketSessionMap.remove(entry.getKey());
				break;
			}
		}
	}

	@Override
	public boolean supportsPartialMessages() {
		return false;
	}

	/**
	 * 给所有在线用户发送消息
	 * @param message
	 * @throws IOException
	 */
	public void broadcast(final Contact message) throws IOException {
		Iterator<Entry<Integer, WebSocketSession>> it =
				userSocketSessionMap.entrySet().iterator();
		//多线程群发
		ThreadPoolExecutor pool = new ThreadPoolExecutor(
				10,
				6,
				10,
				TimeUnit.SECONDS,
				new LinkedBlockingQueue<>()) ;

		while (it.hasNext()) {
			final Entry<Integer, WebSocketSession> entry = it.next();
			//判断该WebSocketSession是否关闭
			if (entry.getValue().isOpen()) {
				// entry.getValue().sendMessage(message);
				pool.submit(() -> {
					try {
						if (entry.getValue().isOpen()) {
							//调用sendMessage方法发送消息
							// entry.getValue().sendMessage(message);
							sendMessageToUser(message);
						}
					} catch (IOException e) {
						e.printStackTrace();
					}
				}) ;
			}
		}
	}


	/**
	 * 给某个用户发送消息
	 *
	 * @param    // 消息接收者id
	 * @param message  消息内容
	 * @throws IOException
	 */
	private void sendMessageToUser(WebSocketSession session,TextMessage message) throws IOException {
//		WebSocketSession webSocketSession = userSocketSessionMap.get(userId);
//		System.out.println(webSocketSession);
//		if (webSocketSession != null && webSocketSession.isOpen()) {
		session.sendMessage(message);
//		}
	}

	public void sendOffMessageToUser(Contact contact) {
		//TODO
		redisUtil.addOffMessage(contact);
		// chatMessage.addChatMessage(contact);
	}

	/**
	 * 给某个用户发送消息
	 * @param contact  消息内容
	 * @throws IOException
	 */
	public void sendMessageToUser(Contact contact) throws IOException {
		int userId = contact.getReceiverId() ;
		WebSocketSession webSocketSession = userSocketSessionMap.get(userId);
		System.out.println(webSocketSession);
		if (webSocketSession != null && webSocketSession.isOpen()) {
			// 用户已登录，直接发送消息
			contact.setType(1);
			TextMessage message = contact.newTextMessage() ;
			sendMessageToUser(webSocketSession,message);
			// TODO
			// chatMessage.addChatMessage(contact);
			redisUtil.addOffMessage(contact);
			System.out.println("发送消息成功");
		} else {
			// 用户未登录，发送离线消息
			contact.setType(0);
			System.out.println("离线消息发送");
			sendOffMessageToUser(contact);
		}
	}
}
